package xyhoo.springframework.beans.factory.xml;

import org.w3c.dom.Document;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import xyhoo.springframework.util.xml.XmlValidationModeDetector;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

public class DefaultDocumentLoader implements DocumentLoader {

    /**
     * 用于配置验证模式语言（schema language） 的 JAXP 属性
     */
    private static final String SCHEMA_LANGUAGE_ATTRIBUTE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";

    /**
     * XSD 模式语言的 JAXP 属性值
     */
    private static final String XSD_SCHEMA_LANGUAGE = "http://www.w3.org/2001/XMLSchema";

    @Override
    public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
                                 ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
        DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);

        DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
        return builder.parse(inputSource);
    }

    /**
     * 创建{@link DocumentBuilderFactory}实例
     *
     * @param validationMode 验证模式：{@link XmlValidationModeDetector#VALIDATION_DTD DTD}或{@link XmlValidationModeDetector#VALIDATION_XSD XSD}
     * @param namespaceAware 是否提供对XML命名空间的支持
     * @return
     * @throws ParserConfigurationException
     */
    protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware) throws ParserConfigurationException {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(namespaceAware);

        if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
            factory.setValidating(true);
            if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
                factory.setNamespaceAware(true);
                try {
                    factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
                } catch (IllegalArgumentException ex) {
                    ParserConfigurationException pcex = new ParserConfigurationException(
                            "Unable to validate using XSD: Your JAXP provider [" + factory +
                                    "] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +
                                    "Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
                    pcex.initCause(ex);
                    throw pcex;
                }
            }
        }
        return factory;
    }

    /**
     * 创建一个JAXP的DocumentBuilder用于解析文档。此方法可以在子类中重写，以添加对解析器的进一步初始化
     *
     * @param factory        用于创建DocumentBuilder的DocumentBuilderFactory
     * @param entityResolver 在 XML 解析过程中，自定义如何获取外部资源（如 DTD、XSD 文件）。XML配置文件中引用外部的DTD或XSD，默认从网络中获取，通过自定义的entityResolver，可以从本地获取
     * @param errorHandler   处理错误
     * @return
     * @throws ParserConfigurationException
     */
    protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory, EntityResolver entityResolver, ErrorHandler errorHandler) throws ParserConfigurationException {
        DocumentBuilder docBuilder = factory.newDocumentBuilder();

        if (null != entityResolver) {
            docBuilder.setEntityResolver(entityResolver);
        }
        if (null != errorHandler) {
            docBuilder.setErrorHandler(errorHandler);
        }
        return docBuilder;
    }
}
